//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

/** NOTE: THIS FILE IS A VERY SIMPLE KERNEL-BASED SCREEN DRIVER.
	IT DOES NOT REPLACE ANY OTHER FORM OF DRIVER AND IS FOR DEBUGGING
	*ONLY*. ONCE DRIVER CODE IS IMPLMENETED THE DRIVER I/O INTERFACE
	MUST BE USED INSTEAD.

	THIS CODE IS NOT EFFICIENT BY DESIGN, IT IS ONLY MEANT TO WORK
	WITHOUT ANY TRICKS OR SPECIAL CASE CODE.

	THE DRIVER ITSELF MAY USE SOME OF THESE FUNCTIONS FOR I/O.
**/

// Includes
#include <mattise.h>
#include <stdint.h>
#include <mem/util.h>
#include <core/portio.h>
#include <screen/conio.h>

// Variables

// the framebuffer location (in case it ever needs to change) and dimensions
static const uint32_t kFrameBuffer = 0xB8000;
static const uint32_t kScreenWidth = 80;
static const uint32_t kScreenHeight = 25;

// the x and y location on the screen
volatile uint32_t kScreenX = 0;
volatile uint32_t kScreenY = 0;

// Functions

// moves the cursor
void kMoveCursor( uint32_t x, uint32_t y )
{
	// check for safe parameters
	if( x >= kScreenWidth || y >= kScreenHeight )
		return;

	// get a word with the location of the cursor
	uint16_t NewCursorLoc = y * kScreenWidth + x;

	// send to the VGA device
	outb( 0x3d4, 14 ); // setting high cursor byte
	outb( 0x3d5, (NewCursorLoc >> 8) & 0xFF );
	outb( 0x3d4, 15 ); // setting low cursor byte
	outb( 0x3d5, NewCursorLoc & 0xFF );
}

// gets the X co-ordinate
uint32_t kGetScreenX()
{
	return kScreenX;
}

// gets the Y co-ordinate
uint32_t kGetScreenY()
{
	return kScreenY;
}

// sets the scren co-ordinates
void kSetScreenCoOrds( uint32_t x, uint32_t y )
{
	kScreenX = x;
	kScreenY = y;
	kMoveCursor( x, y );
}

// clears the screen
void kClearScreen( uint32_t x, uint32_t y )
{
	// clear out the screen using a 64-bit pointer
	uint64_t* fb = (uint64_t*) kFrameBuffer;
	size_t i;
	for( i = (((y * kScreenWidth + x)*sizeof( uint16_t ))/sizeof(uint64_t)); i < (((kScreenWidth * kScreenHeight) * sizeof( uint16_t )) / sizeof(uint64_t)); i++ )
		fb[i] = 0x0F200F200F200F20ULL;

	// x and y become whatever was passed
	kScreenX = x; kScreenY = y;
}

// scrolls the screen when the text goes too far
void kScrollScreen()
{
	// only work if the y co-ordinate is too high
	if( kScreenY >= kScreenHeight )
	{
		// find out how many lines
		size_t numlines = (kScreenY - kScreenHeight) + 1;

		// move it up
		size_t i;
		for( i = 0; i < numlines; i++ )
			memcpy( (void*) (kFrameBuffer -
							(kScreenWidth * sizeof( uint16_t ))),
							(void*) kFrameBuffer,
							((kScreenWidth * kScreenHeight) * sizeof( uint16_t ))
					);

		// set the new x and y locations
		kScreenY -= numlines;

		// clear the last line
		kClearScreen( 0, kScreenY );
	}
}

// writes a character
void kWriteChar( uint8_t c, uint8_t a )
{
	// check for special characters
	if( c == '\r' )
		kScreenX = 0;
	else if( c == '\n' )
	{
		// emulate carriage return behaviour as well
		kScreenX = 0;
		kScreenY++;
	}
	else if( c == 8 )
	{
		if( kScreenX > 0 )
			kScreenX--; // backspace should move back one character
	}
	else if( c == '\t' )
		kScreenX += (kScreenX % 4);
	else
	{
		if( c == 0 )
			return; // don't try to print null chars

		// print the character
		uint16_t* fb = (uint16_t*) kFrameBuffer;
		fb[kScreenY * kScreenWidth + kScreenX] = (a << 8) | c;

		// increment the x co-ordinate
		kScreenX++;
	}

	// move to the next line if needed
	if( kScreenX >= kScreenWidth )
	{
		kScreenX = 0;
		kScreenY++;
	}

	// scroll the screen up, if needed
	kScrollScreen();

	// adjust the cursor position
	kMoveCursor( kScreenX, kScreenY );
}

// writes a string
void kWriteString( char* s, uint8_t a )
{
	// print out the string (until a null character)
	while( *s != 0 )
	{
		// print the character
		kWriteChar( *s, a );

		// increment the pointer
		s++;
	}
}
